package com.bigdata

/**
 * 高阶函数
 */
object Demo11Fun {

  def main(args: Array[String]): Unit = {
    /**
     * 面向对象编程 - 将对象传来传去
     * 1、以对象作为参数
     * 2、以对象作为返回值
     * 需要明确参数的类型，返回值的类型和对象的类型   - 可以使用多态（父类的引用指向子类的对象）
     *
     * 面向函数编程 -- 将函数传来传去  - 高阶函数
     * 1、以函数作为参数  （必须要很熟练）
     * 2、以函数作为返回值 （了解）
     * 需要明确函数的类型
     *
     * 函数的类型 -- 由函数参数类型和返回值类型决定
     * strToInt是一个参数为String返回值为Int类型的函数
     *
     * 函数类型的表示  ：String => Int， 前面是参数类型，后面是返回值类型
     */
    // 一种特殊类型的变量
    def strToInt(str: String): Int = {
      str.toInt
    }

    // 调用函数
    val num: Int = strToInt("100")
    println(num)

    // 对函数进行赋值
    // String => Int 表示变量是函数类型，这个函数的参数是String,返回值是Int
    val strToIntTemp: String => Int = strToInt

    val num1: Int = strToIntTemp("200")
    println(num1)

    /**
     * 1、函数作为参数
     * fun的参数是一个函数（是一个参数为String返回值Int类型的函数）
     * 以函数作为参数编程的作用--- 我们可以将一个代码逻辑中需要变化的部分抽象出来，让调用者以函数的形式传递进来
     */
    def fun(f1: String => Int, str: String): Int = {
      println("fun---before....")
      // 调用参数中的函数，内部的每次调用实现不同的逻辑，具体的逻辑处理是在传递的函数参数中
      val num3: Int = f1(str)
      println("fun---after....")
      // 把调用的结果返回，作为fun函数的结果
      num3
    }

    // 把字符串的数字翻倍
    def f1(str: String): Int = {
      str.toInt * 2
    }

    def f2(str: String): Int = {
      str.toInt - 100
    }

    val num4: Int = fun(f1, "200")
    println(num4) // 400

    val num5: Int = fun(f2, "200")
    println(num5)// 100


    println("==" * 100)

    /**
     * lambda表达式（匿名函数：没有名字的函数）
     * (s: String) => {s.toInt}
     * 前面是参数，后面是函数体和返回值，默认最后一行作为返回值
     *
     * lambda表达式返回值的类型会自动推断
     */
    val f3 = (s: String) => {
      s.toInt / 2
    }

    println(f3("20"))

    val num6: Int = fun(f3, "200")
    println(num6)

    /*
    lambda 表达式实现的函数，作为函数的参数的应用
    函数的逻辑只会调用少次，可以使用匿名函数这种方式
    反之可以定义一个函数来进行传递
    我们在使用spark开发的时候，spark提供好了很多现成的算子，
    算子可以理解成是一个函数，算子只是定义了一部分逻辑，具体的数据怎么处理需要我们传递一个函数来指定
    所以我们在使用RDD算子的时候，一般会选择使用lambda表达式的函数形式来实现。
     */
    val num7: Int = fun((s: String) => {
      s.toInt / 2
    }, "200")

    println(num7)

    // 对匿名函数进行简化
    // 如果只有一个参数，括号可以省略，数据类型也可以省略
    fun(s => {
      s.toInt / 2
    }, "200")

    // 如果函数的代码只有一行，大括号可以省略
    fun(s => s.toInt / 2, "200")

    // 如果匿名函数的参数只在实现方法中使用了一次，可以省略参数，并且在实现代码中使用_代替
    val num8: Int = fun(_.toInt % 2, "200")
    println(num8)

    println("==" * 100)

    // 处理f1函数的参数的逻辑是没有实现的
    def fun1(f1: (String, Int) => Int): Int = {
      println("调用了fun1方法")
      f1("200", 2)
    }

    def xx(str: String, num: Int): Int = {
      println("调用了xx方法")
      str.toInt * num
    }

    println(fun1(xx))

    println(fun1(
      (str: String, num: Int) => {
        str.toInt % num
      }
    ))


    println(fun1(
      (str, num) => str.toInt % num
    ))
    println("**************")
    // 可读性不高
    // 多个参数按照定义的顺序在方法体中只使用了一次，可以使用_代替
    println(fun1(_.toInt % _))


  }

}
